【老万】从0开始学chatGPT(四):做自己的chatGPT,让别人的模型说去吧!
本文是我的 chatGPT 学习心得《从0开始学chatGPT》系列第四篇。欢迎依次阅读:
~~~~
前几天我们一起学习了 chatGPT 的基本工作原理,并用 Python 编程实现了一个罗大佑歌词的三阶马尔可夫链模型“络打油”。剖析完 chatGPT 背后的大语言模型最重要的两个参数“温度”和“脱皮”的用法后,我们还用 Python 为络打油实现了这两个参数。全部代码开源在 https://github.com/zhanyong-wan/lyrics-master,欢迎大家检阅。
看到这里,你是不是不满足于自己土法上马的小模型,已经开始摩拳擦掌、抓耳挠腮、摇头晃脑、上蹿下跳,想要马上用 openAI 公司的原装 API(应用程序接口)访问真正的 chatGPT 背后的大模型了呢?
好,我们今天就安排。这篇文章将介绍如何用 Python 访问 openAI API,解锁 chatGPT 的全部威力。跟前面几篇着重于原理和技术不同,这一次我们聚焦于实操。兄弟姐妹们,撸起袖子,上!
~~~~
要用 openAI 的 API,先得去 https://platform.openai.com/signup 注册一个账号。你也可以用自己已有的谷歌或者微软账号。促销期间,开户就送 18 美元话费,折合跟最新的 gpt-3.5-turbo 模型唠 450 万字。需要注意:预送话费是有保质期的,三个月作废。为了不暴殄天物,必须每月唠 150 万字,每天 5 万字,压力山大。
我们以前说过,openAI 的 API 是按流量计费的,不同模型定价不同。其中 gpt-3.5-turbo 是今年 3 月 1 号刚刚开放的最新模型,也是 chatGPT 自身采用的模型。它的能力和达芬奇(Davinci)模型(在 3 月 1 号以前,这是 oopenAI 开放的大语言模型中最强的)相当,但速度快得多,也更适合对话场景,最最重要的是它的价格是达芬奇的十分之一,每 1000 个记号(约 750 个英文单词或 500 汉字)0.002 美元,完全是跳水价。
如果你不差钱,还可以用自己的语料精调(fine-tune,看成“调情”的同学自动去面壁)openAI 的基础模型,让它更适应你的应用场景。比如精调达芬奇的收费是:学习 1000 个记号 0.02 美元,使用 1000 个记号 0.12 美元(是 gpt-3.5-turbo 价格的 60 倍)。
虽然 openAI API 收的是良心价,但 API 是通过程序访问的。要是程序跑飞了,一样可以让你血流成河。所以我强烈建议开通账号后第一件事就是去设置里把每月消费的上限从缺省的 $120 调低到老婆/老公给你的每月零花钱额度(比如 $20),以免影响家庭和谐。无谓言之不预!
改完额度后,到 https://platform.openai.com/account/api-keys 去生成一个自己专属的 API key(密钥)。这一长串貌似随机的字符是你不能说的秘密。谁要是得到了它,就可以冒充你去调用 openAI API,你来买单。所以,不要告诉你的同学/闺蜜/老公/老婆/前夫/前妻/老板/爱豆/送快递的,更不要截屏发朋友圈,尤其不要写进开源代码。
为安全起见,密钥只在生成的时候显示一次,以后打死 openAI 也不会告诉你。所以,建议你把它保存在一个别人看不见的地方,比如写在皇帝的新衣上放进一个私有的谷歌文档。要是你找不着自己的密钥了,或是发现密钥泄露了,也不用慌,马上在 openAI 网站上挂失就好了。你随时可以再生成一个新的密钥,免费。
openAI 网站提供了一个试验场(playground),可以不写代码就访问 API。这样可以很快试验不同模型、参数和提示的效果。不过,目前 playground 还不支持最新的 gpt-3.5-turbo 模型,用这个模型的试验暂时只能靠编程。
进入试验场,你可以先从下拉菜单中选一个预先定义的样板做你试验的基础。样板就是模型、参数和你要发送给模型的提示内容的组合。
在上面这个例子中,我们选择了一个“根据英语写 Python 程序”的样板。
然后我们可以根据需要选择不同的模型、修改模型参数。看到我们的老朋友“温度”和“脱皮”了吗?在上面这个例子(根据英语写 Python)中,我们选的是擅长写代码的 code-davinci-003 模型,而且把温度调到了 0,不须放屁。
对试验结果满意后,就可以查看对应的代码,把它复制到自己的程序中:
~~~~
我有一个好消息:openAI 的 API 虽然背后的功能非常强大,用好了不容易,但上手简单到了极致,只要一点编程的基础知识就行。
这个 API 是基于 http 和 json 的,我们通过 http GET/POST 命令向 API 发请求,服务器给我们的结果是 json(JavaScript Object Notation)格式的数据。json 是一种适合人类读写的直观的数据格式,几乎所有编程语言都有现成的库去访问 http 和解析 json,所以你尽可以选自己喜欢的语言去使用 openAI API。在试验阶段,我们甚至都不用编程,直接用一些命令行工具就可以访问 API。不得不说,这个设计思想大大降低了使用 openAI API 的门槛,非常聪明。
接下来,我们将一如既往用 Python 完成我们的学习任务。openAI 提供了一个 Python 库,比直接用 Python 的 http 和 json 库更方便一点。但我还是决定不用这个 openai 库了,因为直接用 http 和 json 理解更深刻,也没复杂多少。
每次访问 API 我们都必须提供自己的 API 密钥,以便系统收费。如前所述,千万不要把这个密钥写进代码。我建议定义一个叫 OPENAI_API_KEY 的环境变量,在代码里通过这个环境变量获取密钥。比如,在 Linux 或 macOS 上可以在命令行终端写:
export OPENAI_API_KEY=<你自己的 API 密钥>
openAI API 支持不止一种命令,每种命令都有自己的网址,又叫端点(endpoint)。比如,要知道 openAI API 提供哪些模型,可以用 https://api.openai.com/v1/models 这个端点。我们可以从命令行直接执行
curl https://api.openai.com/v1/models \
-H "Authorization: Bearer $OPENAI_API_KEY"
这里,-H 用于指定一个 http header(报头)。这个报头向 openAI 自报家门:“你好,我的密钥是 XXX,自己人。”
结果(只显示局部)是这样的:
"object": "list",
"data": [
...
{
"id": "gpt-3.5-turbo-0301",
"object": "model",
"created": 1677649963,
"owned_by": "openai",
"permission": ...,
...
},
...
]
我们看到,gpt-3.5-turbo-0301 这个模型是 openai 于 Unix 时间第 1677649963 秒(格林尼治时间 2023 年 05:52:43)建立的。
要使用这个模型,我们得把请求写成 json 格式,再把请求用 POST 命令发给 https://api.openai.com/v1/chat/completions 端点。因为这是一个对话模型,在请求里我们要说明:
模型应该扮演什么角色。
过往对话历史。
你对模型说的话。
有了这三点,模型就可以回答你的请求。
你可以把写程序和 openAI 的模型对话想象成跟 AI 合演一出事先没有写好剧本的戏。程序员相当于导演,AI 是演员,用户是票友。好的导演善于把 AI 迅速准确地带入角色,一镜到底,是不是很大胆?
以模仿罗大佑风格写歌词为例,我们给模型的人设是“你是一个文学修养高深的流行歌曲词作者”。在对话历史里,我们应该提供一些例子,便于模型模仿。注意这些输入都是要算字数的,和模型的回答加起来不能超过 4096 个记号(大约 2000 字)。
下面就是我们的一个请求:
{
"model": "gpt-3.5-turbo-0301",
"messages": [
{ "role": "system",
"content": "你是一个文学修养高深的流行歌曲词作者。"},
{ "role": "user",
"content": "用“风儿轻轻吹”做主题写一首歌词"},
{ "role": "assistant",
"content": "风儿你在轻轻的吹\n吹得那满园的花儿醉\n风儿你要轻轻的吹\n莫要吹落了我的红蔷薇\n春天的花是颗小蓓蕾\n夏季里艳红的更娇美\n秋天它花瓣儿处处飞\n冬季里心碎是为了谁\n风儿你在轻轻的吹\n吹得那满园的花儿醉\n风儿你要轻轻的吹\n莫要吹落了我的红蔷薇"),
{ "role": "user",
"content": "用“初恋”做主题写一首歌词,模仿罗大佑的风格,比如前面这首"},
],
"max_tokens": 1024,
"temperature": 0.8,
"top_p": 1,
"frequency_penalty": 1,
"presence_penalty": 1
}
model 参数指明我们想用的模型。
messages 参数是提示和对话历史:
每段发言要用 role 指明是谁说的(目前只支持三种角色:系统、用户和助手),用 content 指明说话内容。
第一段发言由系统(system)点明 AI 的身份,为接下来的对话定下基调,确保 AI 的回答在我们期望的框架内,把钱花在刀刃上。
接下来通过一个示例给 AI 说戏,帮助它更好地进入角色:用户说用“风儿轻轻吹”做主题写一首歌词,模型应该答“风儿你在轻轻的吹 吹得那满园的花儿醉......”
然后给 AI 提要求:
用“初恋”做主题写一首歌词,模仿罗大佑的风格,比如前面这首。
其它各种参数用于控制模型的性格和行为。比如:回答不要超过多少记号(max_tokens。三体人给地球回复的第一条信息就是“max_tokens: 0, max_tokens: 0, max_tokens: 0”,不幸被叶文洁直接漠视了,导致地球被团灭)、温度、脱皮、frequency_penalty 和 presence_penalty(防止重复车轱辘话)等。
要把这个请求发给 openAI API,Python 程序应该怎么写?
首先,导入基本库:
import json
import requests
json 自然是处理 json 格式用的。requests 则是用来发送 http 命令。
然后从 OPENAI_API_KEY 环境变量拿到 API 密钥。
api_key = os.getenv("OPENAI_API_KEY")
我写了一个函数,它会列出 104 首罗大佑歌曲的名字,让用户挑一首做 AI 模仿的对象。我们可以调用这个函数得到用户选择的歌名和歌词:
sample_title, sample_lyrics = GetSampleSong()
然后创建 http 报头,指定返回的数据格式(json)和密钥:
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
}
创建对话历史(假定 subject 变量放的是想要的歌曲主题):
messages = [
{
"role": "system",
"content": "你是一个文学修养高深的流行歌曲词作者。",
},
{
"role": "user",
"content": f"用“{sample_title}”做主题写一首歌词",
},
{
"role": "assistant",
"content": "\n".join(sample_lyrics),
},
{
"role": "user",
"content": f"用“{subject}”做主题写一首歌词,模仿罗大佑的风格,比如前面这首"
},
]
把对话历史和模型参数包装成一个 json 对象:
data = json.dumps(
{
"model": "gpt-3.5-turbo-0301",
"messages": messages,
"max_tokens": 1024,
"temperature": 0.8,
"top_p": 1,
"frequency_penalty": 1,
"presence_penalty": 1,
}
)
发送请求:
completion_endpoint = "https://api.openai.com/v1/chat/completions"
result = requests.post(completion_endpoint,
headers=headers, data=data)
结果在 result.json() 里,是一个 json 对象,长得这样:
{
'id': 'chatcmpl-6rZeZAjCClzoXacIvjvw33feoO6UY',
'object': 'chat.completion',
'created': 1678226455,
'model': 'gpt-3.5-turbo-0301',
'usage': {
'prompt_tokens': 306,
'completion_tokens': 538,
'total_tokens': 844
},
'choices': [
{
'message': {
'role': 'assistant',
'content': '... 歌词内容 ...'
},
'finish_reason': 'stop',
'index': 0
}
]
}
重点说明一下:
usage 是计费依据:提示和回答分别用了多少记号。
choices 是一个数组,每个元素是一个答案。因为我们在发请求时没有说要多个答案,这个数组只有一个元素。这个元素有个 message 属性是我们最感兴趣的,它有个 content 属性里面放的是 AI 回答的文字。
所以,要拿到 AI 创作的歌词,只需:
answer = result.json()["choices"][0]["message"]["content"]
~~~~
你是不是好奇 AI 演的罗大佑象不象?这是它模仿《风儿轻轻吹》写的《初恋》:
初恋,那是一段长长的故事
初恋,它在心里绽放着花朵
那时我们年轻无忧
只想和你手牵手走到永久
初恋,那是一个美好的回忆
初恋,让人总会悠然神往起来
多少个春夏秋冬过去了
如今还能否再次相逢?
蓦然间想起那些日子里温馨的笑语声音
甜蜜得似醉人迷情的滚烫红酒般沁人心扉
初恋啊 我们依旧相信爱情
初恋啊 只要有勇气就没办法被击败
愿天上有星光为你点亮路程
护住这份感动 让彼此更加坚定
你看,它紧扣了“初恋”的主题,文字还算通顺,还没忘了使用《风儿轻轻吹》中的元素:花儿、四季、心碎,虽然画虎不成,倒也不逊于“你是我生命中最美的相遇”。
我又让它模仿《恋曲1980》写《初恋》。好家伙,这回它膨胀了,直接署名罗大佑,还安排了一位叫 XXX 的歌手演唱:
作词:罗大佑
演唱:XXX
风吹过我的脸庞,呼啦啦的声响
好像在说着我们初恋的曾经
你靠在我肩上,小手握着我的手
所有美好都不曾放弃过自己
回忆里那些日子,燥热闷人却充满甜蜜
刻骨铭心深埋于心底很久以前
(副歌)
初恋是一首动人旋律,
轻柔婉转又令人感伤
无论时间怎样欺骗自己,
总有你和我相遇再次拥抱
街上行走的路人,匆匆来又匆匆去
寂寞中间默默思念彼此倾听
或许这世界变得多么疯狂和残忍
爱情却从未离开每一个年少时分
(副歌)
初恋是一首动人旋律,
轻柔婉转又令人感伤
无论时间怎样欺骗自己,
总有你和我相遇再次拥抱
到现今就算天涯海角也随时可见
内心永远会想起最红火那段岁月
带着笑容面对别离虚幻将如何去应付
祝福还是道别从此看透
(副歌)
初恋是一首动人旋律,
轻柔婉转又令人感伤
无论时间怎样欺骗自己,
总有你和我相遇再次拥抱。
嗯,也算是表达了原歌中反复吟唱的“亲爱的莫再说你我永远不分离”意思。
我让它不要重复,写短一点。它马上出了个压缩版:
风吹过脸庞呼啦啦
告诉我那时候的我们
小手握在一起未放弃
燥热日子里甜蜜忆起
(副歌)
初恋旋律感人柔情,
永远留在心中不变化
无论时间怎样流逝,
你和我相遇重温情话
街上匆忙路人擦肩而过
思念彼此默默倾听
或许世界太疯狂残酷,
爱情从未离开年少时分
(副歌)
初恋旋律感人柔情,
永远留在心中不变化
无论时间怎样流逝,
你和我相遇重温情话。
咋说呢...... 大佑哥,还是你最棒!
我们再来试试温度的影响。上面的例子用的是 0.8 的温度。如果我们想让 AI 更中规中矩,可以选一个更低的温度。比如,用温度 0.2 模仿《风儿轻轻吹》写《初恋》:
初恋是一首动人的歌
唱出了我青春的梦想
那时候我们还年轻
不知道未来会怎样
初恋是一段美好的回忆
让我感受到爱情的甜蜜
虽然已经过去了那么久
但它依然在我的心里
每当夜深人静时分
我总会想起你的容颜
那个炎热的夏天里面
我们相遇,也许注定要分离
初恋啊,你是否还记得
我们曾经走过的小巷
如今岁月已经流逝
只留下了这份难以割舍
初恋啊,你是否还在意
我的思念和眼泪
纵使时间无法停息
对于你,我的心永远不变
好像是更贴近原作的语感了。要是温度改成 1.1,结果可能是这样的:
初恋,那是个久远的回忆
仿佛逐渐蚀去了鲜亮的色彩
今日回首犹如梦境中看到你
或许唯有爱情才真永存于心间
在春天里漫步出校园走过街巷
听欧洲马克思蓝草声不时想道别离难舍难放
当我把握着说分手最后之际遗下祝愿只剩眼泪水留。
退堂风吹动起高尚富丽的绸缎
海盗电台仍然在嘟吧拉嘟呖长条缈空暴散
我突然感觉到整个青春消失于雾霞深处
而来不及拥抱琴音再次响起
请为初恋消化快乐与沉默
抱歉,络打油,我实在是不懂“欧洲马克思蓝草”是哪种草,“嘟吧拉嘟呖长条缈空暴散“的又是什么声音。看来温度高于 1 并没有多少实用价值。
~~~~
今天我们学习了如何通过简单易用的 openAI API 借助 chatGPT 背后的大语言模型打造罗大佑风格写歌词机器人。这只是 GPT 技术诸多应用场景的沧海一粟。期待大家一起尝试,开发出更多基于 GPT 的应用,用 AI 发电,改写时代。
欲知后事如何,I'll be back.
~~~~~~~~~~
猜你会喜欢:
谷歌对微软:代码管理工具哪家强?- 这些年我用过的版本控制系统
控枪越严越安全?- 谈美国枪支问题
小说:美乐火锅店 - 码工写小说会不会有bug?
金庸群侠评职称 - 如何设计一个让人心服口服的等级分制度
后C++演义 - C++ 和它的掘墓人
一起来抄窦唯的作业 - 《Take Care》为什么写得好?
程序员护发秘籍 - 掌握这些工作技巧,包你不脱发
程序员的核心技能 - 脱口秀讲解程序员最重要的技能
~~~~~~~~~~
关注老万故事会公众号:
码字不易,呕心沥血只是希望更多人看到。如果喜欢这篇文章,请不吝订阅、转发、评论。谢谢!🙏